// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
using System.Data.Common;

namespace SnapObjects.Data.Odbc
{
    internal class OdbcHanaAdoDbCommand : OdbcAdoDbCommandBase
    {
        public OdbcHanaAdoDbCommand(
            [NotNull] string sqlText,
            IEnumerable<IAdoDbParameter> parameters,
            bool addExceptionToTransaction,
            CommandType commandType = CommandType.Text)
            : base(sqlText, parameters, addExceptionToTransaction, commandType)
        {
        }

        public override void ExecuteParseOnly(DataContext dataContext)
        {
            try
            {
                base.ExecuteParseOnly(dataContext);
            }
            catch
            {
                throw;
            }
        }

        public override Task ExecuteParseOnlyAsync(DataContext dataContext, CancellationToken cancellationToken = default)
        {
            try
            {
                return base.ExecuteParseOnlyAsync(dataContext);
            }
            catch
            {
                throw;
            }
        }

        protected override string BuildNoExecCommandText(DataContext context, string commandText)
        {
            var sb = new StringBuilder();

            sb.AppendLine("explain plan for");
            sb.AppendLine(commandText);

            return sb.ToString();
        }

        protected override void PreCreateCommand(DataContext dataContext)
        {
            if (!this.IsRegenerateSql)
            {
                base.PreCreateCommand(dataContext);
            }
        }

        protected override void CreateParameters(DataContext dataContext, IDbCommand command)
        {
            if (this.Parameters == null)
            {
                return;
            }

            var isRegenerateSql = this.IsRegenerateSql;

            var sqlBuilder = new StringBuilder();

            var sqlHelper = (OdbcSqlGenerationHelperBase)dataContext.AdoDbSqlProvider.SqlGenerationHelper;

            var typeMapper = dataContext.AdoDbSqlProvider.TypeMapper;

            foreach (var parameter in this.Parameters)
            {
                if (typeof(IEnumerable).IsAssignableFrom(parameter.ClrType)
                        && parameter.ClrType != typeof(string)
                        && parameter.ClrType != typeof(byte[]))
                {
                    this.CreateArrayParameter(
                        command, typeMapper, parameter, (IEnumerable)parameter.Value);
                }
                else
                {
                    var typeMapping = !String.IsNullOrWhiteSpace(parameter.TypeName)
                                    ? typeMapper.FindMapping(parameter.TypeName)
                                    : typeMapper.FindMapping(parameter.ClrType);

                    if (typeMapping != null)
                    {
                        AdoDbParameterBuilder.AddDbParameter((DbCommand)command, typeMapping, parameter);
                    }
                    else
                    {
                        AdoDbParameterBuilder.AddDbParameter((DbCommand)command, parameter);
                    }

                    if (isRegenerateSql)
                    {
                        var dbParameter = (DbParameter)command.Parameters[command.Parameters.Count - 1];

                        this.GenerateSqlHeader(typeMapping, dbParameter, sqlBuilder, sqlHelper);
                    }
                }
            }

            if (isRegenerateSql)
            {
                command.CommandText = this.RegenerateSql(sqlBuilder, command, sqlHelper);
            }
        }

        private void GenerateSqlHeader(
            TypeMapping typeMapping,
            DbParameter dbParameter,
            StringBuilder sqlBuilder,
            OdbcSqlGenerationHelperBase helper)
        {
            if (this.CommandType != CommandType.StoredProcedure)
            {
                sqlBuilder
                .Append(this.GetDbParameterDirection(dbParameter.Direction))
                .Append(" ");
            }

            var parameterName = dbParameter.ParameterName;

            if (parameterName.StartsWith(helper.ParameterIdentifier.ToString()))
            {
                parameterName = parameterName.Remove(0, 1);
            }

            sqlBuilder.Append(parameterName).Append(" ");
            
            if (this.CommandType != CommandType.StoredProcedure)
            {
                if (typeMapping is IOdbcHanaTypeMapping hanaTypeMapping)
                {
                    sqlBuilder.Append(hanaTypeMapping.ReBuildDbTypeName(dbParameter)).Append(" ");
                }
                else
                {
                    sqlBuilder.Append(typeMapping.DbTypeName).Append(" ");
                }
            }
            
            sqlBuilder.Append("=>").Append(helper.ParameterPlaceholder);

            sqlBuilder.Append(",");
        }

        private string RegenerateSql(
            StringBuilder sqlBuilder, IDbCommand command, ISqlGenerationHelper helper)
        {
            if (sqlBuilder.Length > 0)
            {
                sqlBuilder.Remove(sqlBuilder.Length - 1, 1);
            }

            if (this.CommandType != CommandType.StoredProcedure)
            {
                this.RegenerateUpdateSql(sqlBuilder, command, helper);
            }
            else
            {
                this.RegenerateProcedureSql(sqlBuilder, command);
            }

            return sqlBuilder.ToString();
        }

        private void RegenerateUpdateSql(
            StringBuilder sqlBuilder, IDbCommand command, ISqlGenerationHelper helper)
        {
            sqlBuilder.Insert(0, "do (");

            sqlBuilder.AppendLine(") begin");

            var sqlText = command.CommandText.Trim();

            if (!sqlText.EndsWith(helper.StatementTerminator))
            {
                sqlBuilder.Append(sqlText).AppendLine(helper.StatementTerminator);
            }
            else
            {
                sqlBuilder.AppendLine(command.CommandText);
            }

            sqlBuilder.Append("end");
        }

        private void RegenerateProcedureSql(StringBuilder sqlBuilder, IDbCommand command)
        {
            sqlBuilder.Insert(0, SqlCharacters.OpenParentheses)
                .Append(SqlCharacters.CloseParentheses);

            sqlBuilder.Insert(0, command.CommandText).Insert(0, "call ");
        }

        private string GetDbParameterDirection(ParameterDirection paramDirection)
        {
            var direction = "in";

            switch (paramDirection)
            {
                case ParameterDirection.Input:
                {
                    direction = "in";
                }
                break;
                case ParameterDirection.Output:
                {
                    direction = "out";
                }
                break;
                case ParameterDirection.InputOutput:
                {
                    direction = "inout";
                }
                break;
            }

            return direction;
        }

        private bool IsRegenerateSql =>
            !this.SqlText.TrimStart().StartsWith("select", StringComparison.OrdinalIgnoreCase) &&
            !this.SqlText.TrimStart().StartsWith("call", StringComparison.OrdinalIgnoreCase);
    }
}
